/*
 * IM - Image I/O Library
 * Copyright (c) 1996 CENPES/Petrobras, TeCGraf/PUC-Rio.
 * Antonio E. Scuri - scuri@tecgraf.puc-rio.br
 * 
 * IMSGI.C
 * 
 * Contains the implementation of the SGI file format functions.
 *
 * History:
 *          1996.04.05 - Changed the structure of the module to the new format.
 *          1996.06.14 - Finished inicial version.
 *
 */

#include "im.h"
#include "imio.h"
#include "imsgi.h"
#include "binfile.h"


#include <stdlib.h>
#include <memory.h>


/*  File Header Structure. */
/*	2 Magic;          474 */
/*	1 Storage;        0 ou 1 Compression */
/*	1 BPC;            1 ou 2 Bytes Per Pixel Component */
/*	2 Dimension;      1 ou 2 ou 3 */
/*	2 XSize;          Width */
/*	2 YSize;          Height */
/*	2 ZSize;          Number of Channels. B/W=1, RGB=3, RGBA=4 */
/*	4 PixMin;         Minimum Pixel Value */
/*	4 PixMax;         Maximum Pixel Value */
/*	4   Dummy1; */
/*	80  ImageName;*/
/*	4   ColorMap;   0 ou 1 ou 2 ou 3 */
/*	404 Dummy2;*/
/*  512  */

#define SGI_ID  474

/* Compression */
#define SGI_VERBATIM 0
#define SGI_RLE      1


/*  ColorMap Ids */
#define SGI_NORMAL    0
#define SGI_DITHERED  1
#define SGI_SCREEN    2
#define SGI_COLORMAP  3


typedef struct _imFileSGI
{
  binFile* file_bin;          /* the binary file handle */
  unsigned char storage,      /* sgi compression information */
                bpc;          /* bytes per channels */
  unsigned short zsize;       /* number of channels */
  unsigned long *starttab,    /* compression control buffer */
                *lengthtab,   /* compression control buffer */
                min, max,     /* minimum and maximum pixel values used when bpc = 2 */
                color_map_id, /* palette information */
                line_size;    /* line buffer size */
  unsigned char* line_buffer; /* line buffer */
} imFileSGI;


static int imSGIDecodeScanLineByte(unsigned char* optr, unsigned char* iptr, int width)
{
	unsigned char pixel;
  int c = 0, count;

	while (c < width)
	{
		pixel = *iptr++;

		if (!(count = (pixel & 0x7f)))
      break;

    c += count;
    if (c > width)
			return IM_ERR_READ;

		if(pixel & 0x80)
		{
			while (count--)
				*optr++ = *iptr++;
		}
		else
		{
			pixel = *iptr++;
			while (count--)
				*optr++ = pixel;
		}
	}

  if (c < width)
		return IM_ERR_READ;

  return IM_ERR_NONE;
}


static unsigned char imSGIWord2Byte(unsigned short word_value, int diff, int size)
{
  return (unsigned char)(((word_value - diff) * 255) / size);
}

static imSGIDecodeScanLineWord(unsigned char *optr, unsigned short *iptr, int width, unsigned short min, unsigned short max)
{
	unsigned short pixel;
  int c = 0, size = max - min, count;

	while (c < width)
	{
		pixel = *iptr++;

		if (!(count = (pixel & 0x007f)))
      break;

    c += count;
    if (c > width)
			return IM_ERR_READ;

		if(pixel & 0x0080)
		{
			while (count--)
				*optr++ = imSGIWord2Byte(*iptr++, min, size);
		}
		else
		{
			pixel = *iptr++;
			while (count--)
				*optr++ = imSGIWord2Byte(pixel, min, size);
		}
	}

  if (c < width)
		return IM_ERR_READ;

  return IM_ERR_NONE;
}

static void imSGIConvertScanLineWord(unsigned char *map, unsigned short *buffer, int width, unsigned short min, unsigned short max)
{
  int i, size = max - min;

  for (i = 0; i < width; i++)
  {
    map[i] = imSGIWord2Byte(buffer[i], min, size);
  }
}


static imFile* imSGIReadOpen(char* filename, int *err)
{
  int error = IM_ERR_NONE;
  unsigned short word_value, dim;
  imFile *ifile = (imFile*)malloc(sizeof(imFile));
  imFileSGI *file_sgi = (imFileSGI*)malloc(sizeof(imFileSGI));
  binFile* file_bin;

  ifile->file = file_sgi;

  /* opens the binary file for reading with motorola byte order */
  file_bin = binFileOpen(filename, 0, 1);
  if (file_bin == NULL)
  {
    free(file_sgi);
    free(ifile);
    if (err) *err = IM_ERR_OPEN;
    return NULL;
  }

  file_sgi->file_bin = file_bin;

  /* reads the SGI format identifier */
  binFileRead2BytesBuf(file_bin, &word_value, 1);
  if (binFileError(file_bin))
  {
    binFileClose(file_bin);
    free(file_sgi);
    free(ifile);
    if (err) *err = IM_ERR_READ;
    return NULL;
  }

  if (word_value != SGI_ID)
  {
    binFileClose(file_bin);
    free(file_sgi);
    free(ifile);
    if (err) *err = IM_ERR_FORMAT;
    return NULL;
  }

  /* reads the compression information */
  binFileRead1ByteBuf(file_bin, &file_sgi->storage, 1);
  ifile->compress = (file_sgi->storage == SGI_RLE)? IM_DEFAULT: IM_NONE;

  /* reads the number of bits per channel */
  binFileRead1ByteBuf(file_bin, &file_sgi->bpc, 1);

  /* reads the number of dimensions */
  binFileRead2BytesBuf(file_bin, &dim, 1);

  /* reads the image width */
  binFileRead2BytesBuf(file_bin, &word_value, 1);
  ifile->width = word_value;

  /* reads the image height */
  binFileRead2BytesBuf(file_bin, &word_value, 1);
  ifile->height = word_value;

  /* reads the number of channels */
  binFileRead2BytesBuf(file_bin, &file_sgi->zsize, 1);

  /* reads the min pixel value */
  binFileRead4BytesBuf(file_bin, &file_sgi->min, 1);
  /* reads the max pixel value */
  binFileRead4BytesBuf(file_bin, &file_sgi->max, 1);

  /* jump 84 bytes */
  binFileSeekOffset(file_bin, 84);

  /* reads the color map information */
  binFileRead4BytesBuf(file_bin, &file_sgi->color_map_id, 1);

  if (binFileError(file_bin))
  {
    binFileClose(file_bin);
    free(file_sgi);
    free(ifile);
    if (err) *err = IM_ERR_READ;
    return NULL;
  }

  /* procuramos um tipo adequado */
  switch (file_sgi->color_map_id)
  {
  case SGI_NORMAL:
    switch (dim)
    {
    case 1:
      ifile->height = 1;
    case 2:
      ifile->type = IM_MAP;
      ifile->pal_size = 256;
      break;
    case 3:
      switch(file_sgi->zsize)
      {
      case 1:
        ifile->type = IM_MAP;
        ifile->pal_size = 256;
        break;
      case 3:
      case 4:
        ifile->type = IM_RGB;
        ifile->pal_size = 0;
        break;
      default:
        error = IM_ERR_TYPE;
      }
      break;
    default:
      error = IM_ERR_TYPE;
    }
    break;
  case SGI_DITHERED:
    ifile->type = IM_MAP;
    ifile->pal_size = 256;
    break;
  case SGI_SCREEN:
    ifile->type = IM_MAP;
    ifile->pal_size = 256;
    break;
  case SGI_COLORMAP:
  default:
    error = IM_ERR_TYPE;
  }

  if (error != IM_ERR_NONE)
  {
    file_sgi->line_buffer = NULL;
		file_sgi->starttab = NULL;
		file_sgi->lengthtab = NULL;
    if (err) *err = IM_ERR_TYPE;
    return ifile;
  }

  /* jump 404 bytes */
  binFileSeekOffset(file_bin, 404);

	if (file_sgi->storage != SGI_VERBATIM)
	{
		int tablen = ifile->height * file_sgi->zsize;
		file_sgi->starttab = (unsigned long *)malloc(tablen * sizeof(long));
		file_sgi->lengthtab = (unsigned long *)malloc(tablen * sizeof(long));

    /* reads the compression control information */
    binFileRead4BytesBuf(file_bin, file_sgi->starttab, tablen);
    binFileRead4BytesBuf(file_bin, file_sgi->lengthtab, tablen);
	}
  else
  {
		file_sgi->starttab = NULL;
		file_sgi->lengthtab = NULL;
  }

  if (binFileError(file_bin))
  {
	  if (file_sgi->starttab) free(file_sgi->starttab);
	  if (file_sgi->lengthtab) free(file_sgi->lengthtab);
    binFileClose(file_bin);
    free(file_sgi);
    free(ifile);
    if (err) *err = IM_ERR_READ;
    return NULL;
  }

  /* now initialize the imFile control data */

  ifile->split = 1;     /* image is RRR then GGGG then BBBBBB splited */
  ifile->planes[0] = 0; /* sets the default plane order */
  ifile->planes[1] = 1;
  ifile->planes[2] = 2;
  ifile->cur_line = 0;  /* first line */
  ifile->pre_colors = 1;  /* color map is before the image */

  /* Calculate the size of the scan line buffer in bytes */
  file_sgi->line_size = ifile->width * file_sgi->bpc;
  file_sgi->line_buffer = (unsigned char*)malloc(file_sgi->line_size);

  if (err) *err = IM_ERR_NONE;

  return ifile;
}


static imFile* imSGIWriteOpen(char* filename, int width, int height, int type, int compress, int pal_size, int *err)
{
  unsigned long dword_value;
  unsigned short word_value;
  unsigned char dummy[404];
  imFile *ifile = (imFile*)malloc(sizeof(imFile));
  imFileSGI *file_sgi = (imFileSGI*)malloc(sizeof(imFileSGI));
  binFile* file_bin;

  ifile->file = file_sgi;

  /* opens the binary file for writing with motorola byte order */
  file_bin = binFileOpen(filename, 1, 1);
  if (file_bin == NULL)
  {
    free(file_sgi);
    free(ifile);
    if (err) *err = IM_ERR_OPEN;
    return NULL;
  }

  file_sgi->file_bin = file_bin;

  ifile->width = width;
  ifile->height = height;
  ifile->type = type;
  ifile->pal_size = (type == IM_RGB)? 0: pal_size;
  ifile->compress = IM_NONE;

  /* now initialize the imFile control data */

  ifile->split = 1;     /* image is RGB packed */
  ifile->planes[0] = 0; /* sets the default plane order */
  ifile->planes[1] = 1;
  ifile->planes[2] = 2;
  ifile->cur_line = 0;  /* first line */
  ifile->pre_colors = 1;  /* color map is before the image */

  /* we do not know how to compress the SGI image */
  if (compress == IM_COMPRESSED)
  {
    binFileClose(file_bin);
    free(file_sgi);
    free(ifile);
    if (err) *err = IM_ERR_COMP;
    return NULL;
  }

  file_sgi->storage = SGI_VERBATIM;
  file_sgi->bpc = 1;
  file_sgi->zsize = (unsigned short)((type == IM_RGB)? 3: 1);
  file_sgi->color_map_id = SGI_NORMAL;
  file_sgi->min = 0;
  file_sgi->max = 255;

  file_sgi->starttab = NULL;
  file_sgi->lengthtab = NULL;

  file_sgi->line_size = 0;
  file_sgi->line_buffer = NULL;

  /* writes the SGI file header */
  word_value = SGI_ID;
  binFileWrite2BytesBuf(file_bin, &word_value, 1); /* identifier */
  binFileWrite1ByteBuf(file_bin, &file_sgi->storage, 1); /* storage */
  binFileWrite1ByteBuf(file_bin, &file_sgi->bpc, 1); /* bpc */
  word_value = (unsigned short)((type == IM_RGB)? 3: 2);
  binFileWrite2BytesBuf(file_bin, &word_value, 1); /* dimension */
  word_value = (unsigned short)ifile->width;
  binFileWrite2BytesBuf(file_bin, &word_value, 1); /* image width */
  word_value = (unsigned short)ifile->height;
  binFileWrite2BytesBuf(file_bin, &word_value, 1); /* image height */
  word_value = file_sgi->zsize;
  binFileWrite2BytesBuf(file_bin, &word_value, 1); /* zsize */
  dword_value = file_sgi->min;
  binFileWrite4BytesBuf(file_bin, &dword_value, 1); /* min */
  dword_value = file_sgi->max;
  binFileWrite4BytesBuf(file_bin, &dword_value, 1); /* max */
  binFileWrite1ByteBuf(file_bin, dummy, 84); /* dummy + image name */
  dword_value = file_sgi->color_map_id;
  binFileWrite4BytesBuf(file_bin, &dword_value, 1); /* color_map_id */
  binFileWrite1ByteBuf(file_bin, dummy, 404); /* dummy */

  /* tests if everything was ok */
  if (binFileError(file_bin))
  {
    binFileClose(file_bin);
    free(file_sgi);
    free(ifile);
    if (err) *err = IM_ERR_WRITE;
    return NULL;
  }

  if (err) *err = IM_ERR_NONE;

  return ifile;
}


static void imSGIClose(imFile* ifile)
{
  imFileSGI *file_sgi = (imFileSGI*)ifile->file;
  binFile* file_bin = file_sgi->file_bin;

  binFileClose(file_bin);

	if (file_sgi->starttab) free(file_sgi->starttab);
	if (file_sgi->lengthtab) free(file_sgi->lengthtab);
  if (file_sgi->line_buffer) free(file_sgi->line_buffer);
  free(file_sgi);
  free(ifile);
}


static int imSGIReadColors(imFile* ifile, long *colors)
{
  int c;
  imFileSGI *file_sgi = (imFileSGI*)ifile->file;
  binFile* file_bin = file_sgi->file_bin;

	if (file_sgi->color_map_id == SGI_DITHERED)
	{
    int r,g,b;

    static int red[8] = {0, 36, 73, 109, 146, 182, 218, 255};
    static int green[8] = {0, 36, 73, 109, 146, 182, 218, 255};
    static int blue[4] = {0, 85, 170, 255};

    c = 0;

    for (b = 0; b < 4; b++)
      for (g = 0; g < 8; g++)
        for (r = 0; r < 8; r++)
        {
    			colors[c] = imEncodeColor((unsigned char)red[r], (unsigned char)green[g], (unsigned char)blue[b]);
          c++;
        }
  }
  else
  {
    /* creates a gray scale palette */
		for (c = 0; c < 256; c++)
			colors[c] = imEncodeColor((unsigned char)c, (unsigned char)c, (unsigned char)c);
  }

  return IM_ERR_NONE;
}


static int imSGIWriteColors(imFile* ifile, long *colors)
{
  (void)ifile;
  (void)colors;
  return IM_ERR_NONE;
}


static int imSGIReadLine(imFile* ifile, unsigned char *map)
{
  imFileSGI *file_sgi = (imFileSGI*)ifile->file;
  binFile* file_bin = file_sgi->file_bin;

	if (file_sgi->storage == SGI_VERBATIM)
	{
    if (file_sgi->bpc == 1)
      binFileRead1ByteBuf(file_bin, map, ifile->width);
    else
    {
      binFileRead2BytesBuf(file_bin, (unsigned short*)file_sgi->line_buffer, file_sgi->line_size / 2);
		  imSGIConvertScanLineWord(map, (unsigned short*)file_sgi->line_buffer, ifile->width, (unsigned short)file_sgi->min, (unsigned short)file_sgi->max);
    }
	}
	else
	{
    binFileSeekTo(file_bin, file_sgi->starttab[ifile->cur_line]);

    if (file_sgi->bpc == 1)
    {
      binFileRead1ByteBuf(file_bin, file_sgi->line_buffer, file_sgi->lengthtab[ifile->cur_line]);
		  imSGIDecodeScanLineByte(map, file_sgi->line_buffer, ifile->width);
    }
    else
    {
      binFileRead2BytesBuf(file_bin, (unsigned short*)file_sgi->line_buffer, file_sgi->lengthtab[ifile->cur_line] / 2);
		  imSGIDecodeScanLineWord(map, (unsigned short*)file_sgi->line_buffer, ifile->width, (unsigned short)file_sgi->min, (unsigned short)file_sgi->max);
    }
	}

  if (binFileError(file_bin))
    return IM_ERR_READ;     

  /* we have just read one more line */
  ifile->cur_line++;

  /* we can write more than one channel so re-initialize */
  if (ifile->cur_line == ifile->height)
    ifile->cur_line = 0;

  return IM_ERR_NONE;
}


static int imSGIWriteLine(imFile* ifile, unsigned char *map)
{
  imFileSGI *file_sgi = (imFileSGI*)ifile->file;
  binFile* file_bin = file_sgi->file_bin;

  binFileWrite1ByteBuf(file_bin, map, ifile->width);
  if (binFileError(file_bin))
    return IM_ERR_READ;     

  /* we have just write one more line */
  ifile->cur_line++;

  /* we can write more than one channel so re-initialize */
  if (ifile->cur_line == ifile->height)
    ifile->cur_line = 0;

  return IM_ERR_NONE;
}


static int imSGIRegisterCallback(imCallback cb, int id)
{
  (void)cb;
  (void)id;
  /* callback not found */
  return 0;
}


static imFormat _imFormatSGI =
{
  0,
  imSGIRegisterCallback,
  imSGIReadOpen,
  imSGIWriteOpen,
  imSGIClose,
  imSGIReadColors,
  imSGIWriteColors,
  imSGIReadLine,
  imSGIWriteLine,
  NULL,
  NULL
};

imFormat* imFormatSGI(void)
{
  return &_imFormatSGI;
}
